﻿ #region Disclaimer/Info
 
 /////////////////////////////////////////////////////////////////////////////////////////////////
 //
 //   File:		DomainMapper.cs
 //   Website:		http://dexterblogengine.com/
 //   Authors:		http://dexterblogengine.com/About.ashx
 //   Rev:		1
 //   Created:		19/01/2011
 //   Last edit:		19/01/2011
 //   License:		GNU Library General Public License (LGPL)
 //   File:            DomainMapper.cs
 //   For updated news and information please visit http://dexterblogengine.com/
 //   Dexter is hosted to Codeplex at http://dexterblogengine.codeplex.com
 //   For any question contact info@dexterblogengine.com
 //
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #endregion

using System;
using System.Collections.Generic;
using System.Linq;
using ConfOrm;
using ConfOrm.NH;
using ConfOrm.Patterns;
using ConfOrm.Shop.CoolNaming;
using ConfOrm.Shop.Inflectors;
using ConfOrm.Shop.Packs;
using Dexter.Core.Data.Repository.NHibernate.ConfORM;
using Dexter.Domain.Model;
using Dexter.Repository.NHibernate.Configuration;
using Dexter.Repository.NHibernate.ConfORM.DexterNaming;
using Dexter.Repository.NHibernate.Extensions;
using Dexter.Repository.NHibernate.UserTypes;
using NHibernate;
using NHibernate.Cfg.MappingSchema;
using CultureInfo = System.Globalization.CultureInfo;
using MailAddress = System.Net.Mail.MailAddress;

namespace Dexter.Repository.NHibernate.ConfORM {
	/// <summary>
	/// 	The implementation of <see cref = "IDomainMapper" />.
	/// </summary>
	public class DomainMapper : IDomainMapper {
		readonly IEnumerable <Type> domainEntities;
		readonly Mapper mapper;
		readonly ObjectRelationalMapper orm;

		/// <summary>
		/// Initializes a new instance of the <see cref="DomainMapper"/> class.
		/// </summary>
		/// <param name="configuration">The configuration.</param>
		/// <param name="nhConfiguration">The nh configuration.</param>
		public DomainMapper ( global::NHibernate.Cfg.Configuration configuration, INHibernateConfiguration nhConfiguration ) {
			Type[] tablePerClassHierarchy = new[] {
				typeof ( Item )
			};

			this.domainEntities = typeof ( EntityBase )
				.Assembly.GetTypes ( )
				.Where ( t => ( typeof ( EntityBase ).IsAssignableFrom ( t ) && !t.IsGenericType ) )
				.ToList ( );

			IEnumerable <Type> tablePerClassEntities = typeof ( EntityBase )
				.Assembly.GetTypes ( )
				.Where ( t => ( typeof ( EntityBase ) == t.BaseType && !tablePerClassHierarchy.Contains ( t ) ) )
				.ToList ( );

			this.orm = new ObjectRelationalMapper ( );

			// Mappings
			this.orm.TablePerClass ( tablePerClassEntities );
			this.orm.TablePerClassHierarchy ( tablePerClassHierarchy );
			this.orm.Complex <CultureInfo> ( );
			this.orm.ManyToMany <Item , Category> ( );
			this.orm.ManyToMany <User , Role> ( );
			

			this.orm.Patterns.Lists.Remove ( orm.Patterns.Lists.Single ( p => p.GetType ( ) == typeof ( ListCollectionPattern ) ) );

			IPatternsAppliersHolder patternsAppliers =
				( new SafePropertyAccessorPack ( ) )
					.Merge ( new OneToOneRelationPack ( orm ) )
					.Merge ( new BidirectionalManyToManyRelationPack ( orm ) )
					.Merge ( new BidirectionalOneToManyRelationPack ( orm ) )
					.Merge ( new ConfOrm.Shop.Packs.DiscriminatorValueAsEnumValuePack <Item , ItemDiscriminatorMap> ( orm ) )
					.Merge ( new CoolColumnsNamingPack ( orm ) )
					.Merge ( new TablePerClassPack ( ) )
					.Merge ( new PluralizedTablesPack ( orm, new EnglishInflector ( ), nhConfiguration ) )
					.Merge ( new ListIndexAsPropertyPosColumnNameApplier ( ) );
					
			orm.Patterns.PoidStrategies.Add ( new IdentityPoidPattern ( ) );

			this.mapper = new Mapper ( orm , patternsAppliers );

			this.mapper.TypeDef <DateTime , DateTimeUtc> ( );
			this.mapper.TypeDef <DateTime? , DateTimeUtc> ( );
			this.mapper.TypeDef <System.Version , VersionUserType> ( );
			this.mapper.TypeDef <MailAddress , Dexter.Repository.NHibernate.UserTypes.MailAddress> ( );
			this.mapper.TypeDef <CultureInfo , Dexter.Repository.NHibernate.UserTypes.CultureInfo> ( );

			this.mapper.AddPropertyPattern ( mi => mi.Name == "Title" && mi.GetPropertyOrFieldType ( ) == typeof ( string ) , pm => pm.Length ( 500 ) );
			this.mapper.AddPropertyPattern ( mi => mi.Name == "Slug" && mi.GetPropertyOrFieldType ( ) == typeof ( string ) , pm => pm.Length ( 500 ) );
			this.mapper.AddPropertyPattern ( mi => mi.Name == "Name" && mi.GetPropertyOrFieldType ( ) == typeof ( string ) , pm => pm.Length ( 500 ) );
			this.mapper.AddPropertyPattern ( mi => mi.GetPropertyOrFieldType ( ) == typeof ( Uri ) , pm => pm.Length ( 1000 ) );
			this.mapper.AddPropertyPattern ( mi => mi.GetPropertyOrFieldType ( ) == typeof ( CultureInfo ) , pm => pm.Length ( 6 ) );
			this.mapper.AddPropertyPattern ( mi => mi.Name.StartsWith ( "Message" ) && mi.GetPropertyOrFieldType ( ) == typeof ( string ) , pm => pm.Type ( NHibernateUtil.StringClob ) );

			this.mapper.AddPropertyPattern(mi => mi.Name == "SerializedConfiguration" && mi.GetPropertyOrFieldType() == typeof(string), pm => pm.Type(NHibernateUtil.StringClob));

			this.mapper.Customize <Domain.Model.PluginData> ( map => map.Property ( itm => itm.Data , pm => pm.Type ( NHibernateUtil.StringClob ) ) );
			this.mapper.Customize <Domain.Model.Plugin> ( map => map.Property ( itm => itm.Description , pm => pm.Type ( NHibernateUtil.StringClob ) ) );
			this.mapper.Customize <Domain.Model.Configuration> ( map => map.Property ( itm => itm.CreationDate , pm => pm.Type ( NHibernateUtil.UtcDateTime ) ) );
			this.mapper.Customize <Domain.Model.Configuration> ( map => map.Property ( itm => itm.UpdateDate , pm => pm.Type ( NHibernateUtil.UtcDateTime ) ) );
			this.mapper.Customize <User> ( cm => cm.Property ( user => user.Username , n => n.Unique ( true ) ) );
			this.mapper.Customize<Dexter.Domain.Model.Version> ( cm => cm.Property ( user => user.Key, n => n.Column ( "ObjectKey" ) ) );
			this.mapper.Customize <EmailMessage> ( cm => cm.Property ( user => user.Body , n => n.Type ( NHibernateUtil.StringClob ) ) );
			this.mapper.Customize <TrackBack> ( cm => cm.Property ( user => user.Excerpt , n => n.Length ( 1000 ) ) );
			this.mapper.Customize<Item> ( map => map.Property ( itm => itm.FormattedBody, pm => pm.Type ( NHibernateUtil.StringClob ) ) );

			string commentsTableName = configuration != null
										? global::NHibernate.Dialect.Dialect.GetDialect ( configuration.Properties ).Qualify ( configuration.GetProperty ( global::NHibernate.Cfg.Environment.DefaultCatalog ), configuration.GetProperty ( global::NHibernate.Cfg.Environment.DefaultSchema ), "Dexter_Comments" )
										: "Dexter_Comments";

			this.mapper.Customize<Item> ( map => map.Property ( itm => itm.TotalComments, pm => pm.Formula ( string.Format ( "(Select Count({0}.Id) from {0} Where {0}.ItemId = Id and {0}.Approved = 1 AND {0}.IsSpam = 0)", commentsTableName ) ) ) );
			this.mapper.Customize <Item> ( map => map.Collection ( itm => itm.ApprovedComments , pm => pm.Where ( "Approved = 1" ) ) );
			this.mapper.Customize <Item> ( map => map.Collection ( itm => itm.UnApprovedComments , pm => pm.Where ( "Approved = 0" ) ) );


			// page structure is a view over the Page object and should be used in a readonly way);)
			this.mapper.Class <PageStructure> ( map => map.Where ( "EntityType = " + ( int ) ItemDiscriminatorMap.Page ) );
			this.mapper.Class <PageStructure> ( cm => cm.Table ( "Dexter_Items" ) );

			#region Defining Database structure

			//BlogRoll
			this.mapper.NotNullable <BlogRoll> ( x => x.Link );
			this.mapper.NotNullable <BlogRoll> ( x => x.Name );
			this.mapper.Unique <BlogRoll> ( x => x.Link );
			this.mapper.Unique <BlogRoll> ( x => x.Name );

			//Category
			this.mapper.NotNullable<Category> ( x => x.Name );
			this.mapper.Unique<Category> ( x => x.Name );

			//Comment
			this.mapper.NotNullable <Comment> ( x => x.Name );
			this.mapper.NotNullable <Comment> ( x => x.Message );
			this.mapper.NotNullable <Comment> ( x => x.Email );

			//Configuration
			this.mapper.NotNullable <Domain.Model.Configuration> ( x => x.SerializedConfiguration );

			//EmailMessage
			this.mapper.NotNullable <EmailMessage> ( x => x.Sender );
			this.mapper.NotNullable <EmailMessage> ( x => x.Body );
			this.mapper.NotNullable <EmailMessage> ( x => x.Subject );

			//Item
			this.mapper.UniqueKey <Item> ( x => x.Slug , "IX_EntityType" );
			this.mapper.Class <Item> ( map => map.Discriminator ( dm => dm.Column ( cm => cm.UniqueKey ( "IX_EntityType" ) ) ) );
			this.mapper.NotNullable <Item> ( x => x.Title );
			this.mapper.NotNullable <Item> ( x => x.Slug );

			//PageStructure
			this.mapper.NotNullable <PageStructure> ( x => x.Title );
			this.mapper.NotNullable <PageStructure> ( x => x.Slug );

			//PingSite
			this.mapper.NotNullable <PingSite> ( x => x.SiteUrl );

			//Referrer
			this.mapper.NotNullable <Referrer> ( x => x.Url );
			this.mapper.NotNullable <Referrer> ( x => x.ReferrerUrl );

			//Role
			this.mapper.NotNullable <Role> ( x => x.Name );
			this.mapper.NotNullable <Role> ( x => x.LoweredName );
			this.mapper.Index<Role> ( "RoleKey", x => x.Name, x => x.LoweredName );

			//TagItem
			this.mapper.NotNullable <TagItem> ( x => x.Name );
			this.mapper.NotNullable <TagItem> ( x => x.Item );
			this.mapper.Index<TagItem> ( "TagKey", x => x.Name, x => x.Item );

			//TrackBack
			this.mapper.NotNullable <TrackBack> ( x => x.Title );
			this.mapper.NotNullable <TrackBack> ( x => x.Name );
			this.mapper.NotNullable <TrackBack> ( x => x.Url );
			this.mapper.NotNullable <TrackBack> ( x => x.Item );
			this.mapper.Index<TrackBack> ( "TrackKey", x => x.Url, x => x.Item );

			//User
			this.mapper.NotNullable <User> ( x => x.Username );
			this.mapper.NotNullable <User> ( x => x.Password );
			this.mapper.Unique <User> ( x => x.Username );

			//Version
			this.mapper.NotNullable<Dexter.Domain.Model.Version> ( x => x.Key );
			this.mapper.NotNullable<Dexter.Domain.Model.Version> ( x => x.Value );
			this.mapper.Index<Dexter.Domain.Model.Version> ( "VerKey", x => x.Key, x => x.Value );

			//View
			this.mapper.NotNullable <View> ( x => x.IP );
			this.mapper.NotNullable <View> ( x => x.Item );

			//Vote
			this.mapper.NotNullable <Vote> ( x => x.IP );
			this.mapper.NotNullable <Vote> ( x => x.Item );

			//Plugin
			this.mapper.NotNullable<Plugin>(x => x.Identifier);
			this.mapper.NotNullable<Plugin>(x => x.IsInstalled);
			this.mapper.NotNullable<Plugin>(x => x.Name);
			this.mapper.NotNullable<Plugin>(x => x.Version);
			this.mapper.NotNullable<Plugin>(x => x.Enabled);

			//PluginData
			this.mapper.NotNullable<PluginData>(x => x.Parent);
			this.mapper.NotNullable<PluginData>(x => x.Key);
			this.mapper.NotNullable<PluginData>(x => x.Data);

			orm.Cascade<Item, Category>(CascadeOn.All.Include(CascadeOn.DeleteOrphans));
			orm.Cascade<Category, Item>(CascadeOn.None);

			orm.Cascade<Item, Comment>(CascadeOn.All.Include(CascadeOn.DeleteOrphans));
			orm.Cascade<Comment, Item>(CascadeOn.None);

			orm.Cascade<Item, TrackBack>(CascadeOn.All.Include(CascadeOn.DeleteOrphans));
			orm.Cascade<TrackBack, Item>(CascadeOn.None);

			orm.Cascade<Item, Vote>(CascadeOn.All.Include(CascadeOn.DeleteOrphans));
			orm.Cascade<Vote, Item>(CascadeOn.None);

			orm.Cascade<Item, TagItem>(CascadeOn.All.Include(CascadeOn.DeleteOrphans));
			orm.Cascade<TagItem, Item>(CascadeOn.None);

			orm.Cascade<Plugin, PluginData>(CascadeOn.All.Include(CascadeOn.DeleteOrphans));
			orm.Cascade<PluginData, Plugin>(CascadeOn.None);

			#endregion
		}

		/// <summary>
		/// 	Gets the domain entities.
		/// </summary>
		/// <value>The domain entities.</value>
		public IEnumerable <Type> DomainEntities {
			get { return this.domainEntities; }
		}

		/// <summary>
		/// 	Gets the orm.
		/// </summary>
		/// <value>The orm.</value>
		public ObjectRelationalMapper Orm {
			get { return this.orm; }
		}

		/// <summary>
		/// 	Gets the mapper.
		/// </summary>
		/// <value>The mapper.</value>
		public Mapper Mapper {
			get { return this.mapper; }
		}

		/// <summary>
		/// 	Gets the mapping.
		/// </summary>
		/// <value>The mapping.</value>
		public HbmMapping Mapping {
			get { return this.Mapper.CompileMappingFor ( domainEntities ); }
		}

		#region IDomainMapper Members

		/// <summary>
		/// 	Gets the HBM mapping.
		/// </summary>
		/// <value>The HBM mapping.</value>
		public HbmMapping HbmMapping {
			get { return this.Mapper.CompileMappingFor ( domainEntities ); }
		}

		/// <summary>
		/// 	Gets the HBM mappings.
		/// </summary>
		/// <value>The HBM mappings.</value>
		public IEnumerable <HbmMapping> HbmMappings {
			get { return this.Mapper.CompileMappingForEach ( domainEntities ).ToList ( ); }
		}

		#endregion
	}
}
